module  iic_dri
#(
parameter  slave_addr = 7'b10100000,
parameter  clk_freq   = 26'd50_000_000,

//mode1:100k;mode2:400k;mode3:3.4M
parameter  iic_freq   = 18'd250_000

)
(
input           clk        ,
input           rst_n      ,

input           iic_exec,      //触发执行信号
input           bit_ctrl,      //8/16字节控制位
input           iic_rh_wl,     //读写控制位read(1)  write(0)
input  [15:0]   iic_addr,      //器件内地址
input   [7:0]   iic_data_w,    //写入的数据
output  [7:0]   iic_data_r,    //读出的数据 (从机发给主机的数据)
output  reg     iic_done,
output  reg     iic_ack,       //应答位，0表示正确应答
output  reg     scl,
inout           sda,
output  reg     dri_clk       //驱动I2C操作的驱动时钟
//驱动iic_scl的时钟频率一般是scl时钟的4倍
);

//状态机切换，各种状态定义
localparam  st_idle     = 8'b0000_0001; //空闲状态
localparam  st_sladdr   = 8'b0000_0010; //发送器件地址(slave address)
localparam  st_addr16   = 8'b0000_0100; //发送16位字地址
localparam  st_addr8    = 8'b0000_1000; //发送8位字地址
localparam  st_data_wr  = 8'b0001_0000; //写数据(8 bit)
localparam  st_addr_rd  = 8'b0010_0000; //发送器件地址读
localparam  st_data_rd  = 8'b0100_0000; //读数据(8 bit)
localparam  st_stop     = 8'b1000_0000; //结束I2C操作


//在I2C通信中，SDA线的方向需要根据通信状态动态切换。
//写操作时，SDA线配置为输出模式；读操作时，SDA线配置为输入模式。

reg            sda_dir   ; //I2C数据(SDA)方向控制
reg            sda_out   ; //SDA输出信号
reg            st_done   ; //状态结束
reg            wr_flag   ; //写标志
reg    [ 6:0]  cnt       ; //计数
reg    [ 7:0]  cur_state ; //状态机当前状态
reg    [ 7:0]  next_state; //状态机下一状态
reg    [15:0]  addr_t    ; //器件内寄存器地址
reg    [ 7:0]  data_r    ; //读取的数据
reg    [ 7:0]  data_wr_t ; //I2C需写的数据的临时寄存
reg    [ 9:0]  clk_cnt   ; //分频时钟计数

//wire define
wire          sda_in     ; //SDA输入信号(数据)
wire   [8:0]  clk_divide ; //模块驱动时钟的分频系数

//(sda_dir,是判断条件，确定sda是输出还是输入模式)
assign  sda = sda_dir ? sda_out : 1'bz;
assign  sda_in  = sda ;                          //SDA数据输入(主设备释放总线后)
assign  clk_divide = (clk_freq/iic_freq) >> 2'd2;//模块驱动时钟的分频系数（200->50）


//(clk_divide[8:1])此时是25，因为需要的是50倍，所以需要在25时候翻转
always@(posedge clk or negedge rst_n)
  begin
    if(!rst_n)
    begin
      dri_clk <= 1'b0 ;
      clk_cnt <= 10'b0; //分频时钟计数器
    end
    else if(clk_cnt == ((clk_divide[8:1])-9'd1)
      begin
        clk_cnt <= 10'b0;
        dri_clk = ~ dri_clk;
      end
    else
      clk_cnt = clk_cnt + 10'b1;
  end

//状态转换 同步时钟
always@(posedge dri_clk or negedge rst_n)
  begin
    if(!rst_n)
      cur_state <= st_idle;
    else 
      cur_state <= next_state;
   end

//组合逻辑判断状态转移：
//always@(posedge dri_clk or negedge rst_n)为时序逻辑的判断条件
always@(*)
  begin
    next_state = st_idle;
    case(cur_state)
      st_idle : 
        begin
          if(iic_exec)
            next_state <= st_sladdr;
          else
            next_state <= st_idle;
        end
      st_sladdr :
        begin
          if(st_done)
            begin
              if(bit_ctrl)
                next_state <= st_addr16;
            else
                next_state <= st_addr8;
            end
          else
            next_state <= st_sladdr;
        end
      st_addr16 :
        begin
          if(st_done)
            next_state <= st_addr8;
          else
            next_state <= st_addr16;
        end
      st_addr8 : 
        begin
          if(st_done)
            begin
              if(wr_flag)
                next_state <= st_addr_rd;
              else
                next_state <= st_addr8;
            end
          else
            next_state <=st_data_wr;
        end
        st_data_wr :
          begin
            if(st_done)
              next_state <= st_stop;
            else
              next_state <= st_data_wr;
          end
        st_addr_rd :
          begin
            if(st_done)
              next_state <= st_data_rd;
            else
              next_state <= st_addr_rd;
          end
        st_data_rd :
          begin
            if(st_done)
              next_state <= st_stop;
            else
              next_state <= st_data_rd;
          end
        st_stop :
          begin
            if(st_done)
              next_state <= st_idle;
            else
              next_state <= st_stop;
          end
        default : next_state= st_idle;
    endcase
  end

//时序逻辑表示状态输出：
always@(posedge dri_clk or negedge rst_n)
  begin
    if(!rst_n)
      begin
        sda_dir <= 1'b1;
        sda_out <= 1'b1;
        scl     <= 1'b1;
      
        iic_done   <= 1'b0;
        iic_data_r <= 1'b0;
        iic_ack    <= 1'b0;
        st_done    <= 1'b0;
        cnt        <= 1'b0;
        addr_t     <= 1'b0;//初始化为 0，表示尚未设置目标地址。在 I2C 写操作中，目标地址会存储到 addr_t。
        data_r     <= 1'b0;
        data_wr_t  <= 1'b0;
        wr_flag    <= 1'b0;//read(1) write(0),读写判断
      end
    else
      begin
        //状态切换和计数器除了在特定状态被打断其他时刻都在切换和计数
        st_done <= 1'b0 ;
        cnt     <= cnt + 7'b1 ; 
        case(cur_state)
          st_idle :
            begin
              sda_dir <= 1'b1;
              sda_out <= 1'b1;
              scl     <= 1'b1; 
              i2c_done<= 1'b0;                     
              cnt     <= 7'b0;
              if(iic_exec)
                begin
                  wr_flag   <= iic_rh_wl;
                  addr_t    <= iic_addr;//器件内地址的寄存器
                  data_wr_t <=iic_data_w;
                end
            end
          st_sladdr :
            begin
              case(cnt)
                7'd1 : sda_out <=1'b0;
              //7'd2 : sda_out <=1'b0;
                7'd3 : scl     <=1'b0;//scl,高时，sda,由高变低(start)
                7'd4 : sda_out <=SLAVE_ADDR[6]; 
              //7'd5 : scl     <=1'b0;
                7'd6 : scl     <=1'b1;
                7'd7 : scl     <=1'b0; 
                7'd8 : sda_out <=SLAVE_ADDR[5];    
                7'd9 : scl     <=1'b1;
              //7'd10 : scl    <=1'b1;
                7'd11 : scl    <=1'b0;
                7'd12 : sda_out<=SLAVE_ADDR[4];                
                7'd13 : scl    <=1'b1;
              //7'd14 : scl    <=1'b1;
                7'd15 : scl    <=1'b0;
                7'd16 : sda_out<=SLAVE_ADDR[3];              
                7'd17 : scl    <=1'b1;
              //7'd18 : scl    <=1'b1; 
                7'd19 : scl    <=1'b0;
                7'd20 : sda_out<=SLAVE_ADDR[2];             
                7'd21 : scl    <=1'b1;
              //7'd22 : scl    <=1'b1;  
                7'd23 : scl    <=1'b0;
                7'd24 : sda_out<=SLAVE_ADDR[1];             
                7'd25 : scl    <=1'b1;s
              //7'd26 : scl    <=1'b1; 
                7'd27 : scl    <=1'b0;
                7'd28 : sda_out<=SLAVE_ADDR[0];               
                7'd29 : scl    <=1'b1;
              //7'd30 : scl    <=1'b1; 
                7'd31 : scl    <=1'b0;
                7'd32 : sda_out<=1'b0;  //0:写            
                7'd33 : scl    <=1'b1;  //此时写操作标志失效  r/w 读写控制位 
              //7'd34 : scl    <=1'b1; 
                7'd35 : scl    <=1'b0;  
                7'd36 : 
                    sda_dir <= 1'b0;  //将 SDA 设置为输入模式
                    sda_out <= 1'b1;  //释放总线
                7'd37 : scl    <=1'b1; //此时从机可以在scl高电平发送信号
                7'd38 : 
                  begin
                    st_done <= 1'b1;
                      if(sda_in == 1'b1)  //高电平表示从机未应答
                        iic_ack <=1'b1;
                  end 
                7'd39 : 
                  begin
                    scl  <=1'b0;
                    cnt  <=7'd0;
                  end
                default: ;  //无默认值，表示空操作
              endcase
            end
        //sda_dir 为高电平时为输出模式，主设备控制，为低电平时为输入模式    
          st_addr16: 
            begin
              case(cnt)
                7'd0 :
                begin                     
                  sda_dir <= 1'b1 ;            
                  sda_out <= addr_t[15];       //传送字地址
                end
                //在 I2C 通信中，scl 是时钟线，用于同步数据的传输。
                //每个数据位的传输都需要一个完整的时钟周期，
                //即 scl 从低电平到高电平再回到低电平的过程。
                7'b1 : scl <=1'b1;
              //7'd2 : scl <=1'b1;
                7'd3 : scl <=1'b0;
                7'd4 : sda_out <=addr_t[14];              
                7'd5 : scl     <=1'b1;
              //7'd6 : scl     <=1'b1;
                7'd7 : scl     <=1'b0; 
                7'd8 : sda_out <=addr_t[13];               
                7'd9 : scl     <=1'b1;
              //7'd10 : scl     <=1'b1;
                7'd11 : scl     <=1'b0;
                7'd12 : sda_out <=addr_t[12];
                7'd13 : scl     <=1'b1;
              //7'd14 : scl     <=1'b1;
                7'd15 : scl     <=1'b0;
                7'd16 : sda_out <=addr_t[11];
                7'd17 : scl     <=1'b1;
                7'd18 : scl     <=1'b1;
                7'd19 : scl     <=1'b0;
                7'd20 : sda_out <=addr_t[10];
                7'd21 : scl     <=1'b1;
              //7'd22 : scl     <=1'b1;
                7'd23 : scl     <=1'b0;
                7'd24 : sda_out  <=addr_t[9];
                7'd25 : scl     <=1'b1;
              //7'd26 : scl     <=1'b1;
                7'd27 : scl     <=1'b0;
                7'd28 : sda_out   <=addr_t[8];
                7'd29 : scl     <=1'b1;
              //7'd30 : scl     <=1'b1;
                7'd31 : scl     <=1'b0;   //在scl结束一个周期之后
                7'd32 : 
                  begin
                    sda_dir <= 1'b0;  //释放总线，等待从设备应答信号
                    sda_out <= 1'b1;  //上拉，避免主从设备通信冲突
                  end
                7'd33 : scl     <=1'b1;
                7'd34 : 
                  begin
                    st_done <=1'b1;
                    if(sda_in == 1'b1)  //高电平表示从机未应答
                      iic_ack <=1'b1;
                  end
                7'd35 : 
                  begin
                    scl <=1'b0;
                    cnt <=1'b0;
                  end
                default : ;
              endcase
            end
            
          st_addr8 : 
            begin
              case(cnt)
                7'd0 : 
                  begin
                    sda_dir <=1'b1;//将总线设为输出模式
                    sda_out <=addr_t[7];//保证在sdl被拉高时可以稳定传输
                  end
                7'd1 :scl <= 1'b1;
                //7'd2 :scl <= 1'b1;
                7'd3 :scl <= 1'b0;
                7'd4 :sda_out <= addr_t[6];
                7'd5 :scl <= 1'b1;
                //7'd6 :scl <= 1'b1;
                7'd7 :scl <= 1'b0;
                7'd8 :sda_out <= addr_t[5];
                7'd9 :scl <= 1'b1;
               //7'd10 :scl <= 1'b1;
               7'd11 :scl <= 1'b0;
               7'd12 :sda_out <= addr_t[4];
               7'd13 :scl <= 1'b1;
               //7'd14 :scl <= 1'b1;
               7'd15 :scl <= 1'b0;
               7'd16 :sda_out <= addr_t[3];
               7'd17 :scl <= 1'b1;
               //7'd18 :scl <= 1'b1;
               7'd19 :scl <= 1'b0;
               7'd20 :sda_out <= addr_t[2];
               7'd21 :scl <= 1'b1;
               //7'd22 :scl <= 1'b1;
               7'd23 :scl <= 1'b0;
               7'd24 :sda_out <= addr_t[1];
               7'd25 :scl <= 1'b1;
               //7'd26 :scl <= 1'b1;
               7'd27 :scl <= 1'b0;
               7'd28 :sda_out <= addr_t[0];
               7'd29 :scl <= 1'b1;
               //7'd30 :scl <= 1'b1;
               7'd31 :scl <= 1'b0;//在scl结束一个周期之后
               7'd32 :
                 begin
                   sda_dir <=1'b0;
                   sda_out <=1'b1;
                 end
               7'd33 :scl <= 1'b1;
               7'd34 :
                 begin
                   st_done <=1'b1;
                     if(sda_in == 1'b1)//从机正常应答会将sda总线拉低
                       iic_ack <= 1'b1;//高电平表示从机未应答
                 end
               7'd35 :
                 begin
                   scl <= 1'b0;
                   cnt <= 1'b0;
                 end
                default : ;
              endcase
            end
          //写数据需要主设备控制sda线，也就需要设为输出模式         
          st_data_wr :
            begin
              case(cnt)
                7'd0 : 
                  begin
                    sda_dir <=1'b1;
                    sda_out <=data_wr_t[7];
                  end 
                7'd1 : scl <=1'b1;
              //7'd2 : scl <=1'b1;
                7'd3 : scl <=1'b0;
                7'd4 : sda_out <=data_wr_t[6];
                7'd5 : scl <=1'b1;
              //7'd6 : scl <=1'b1;
                7'd7 : scl <=1'b0;
                7'd8 : sda_out <=data_wr_t[5];
                7'd9 : scl <=1'b1;
             //7'd10 : scl <=1'b1;
               7'd11 : scl <=1'b0;
               7'd12 : sda_out <=data_wr_t[4];
               7'd13 : scl <=1'b1;
             //7'd14 : scl <=1'b1;
               7'd15 : scl <=1'b0;
               7'd16 : sda_out <=data_wr_t[3];
               7'd17 : scl <=1'b1;
             //7'd18 : scl <=1'b1;
               7'd19 : scl <=1'b0;
               7'd20 : sda_out <=data_wr_t[2];
               7'd21 : scl <=1'b1;
             //7'd22 : scl <=1'b1;
               7'd23 : scl <=1'b0;
               7'd24 : sda_out <=data_wr_t[1];
               7'd25 : scl <=1'b1;
             //7'd26 : scl <=1'b1;
               7'd27 : scl <=1'b0;
               7'd28 : sda_out <=data_wr_t[0];
               7'd29 : scl <=1'b1;
             //7'd30 : scl <=1'b1;
               7'd31 : scl <=1'b0;
               7'd32 : 
                 begin
                   sda_dir <= 1'b0;
                   sda_out <= 1'b1;//释放总线，拉高sda等待从机应答
                 end
               7'd33 : scl <=1'b1; //需要确定在scl高电平时采样，等待从机应答信号
               7'd34 : 
                 begin
                   st_done <= 1'b1;
                   if(sda_in == 1'b1)
                     iic_ack <=1'b1;
                 end
               7'd35 : 
                 begin
                   scl <=1'b0;
                   cnt <=1'b0;
                 end
                default : ;
              endcase
            end
          st_addr_rd : 
            begin
              case(cnt)
                7'd1 : scl <=1'b1;
              //7'd2 : scl <=1'b1;
                7'd3 : scl <=1'b0;
                7'd4 : sda_out <=SLAVE_ADDR[6];
                7'd5 : scl <=1'b1;
              //7'd6 : scl <=1'b1;
                7'd7 : scl <=1'b0;
                7'd8 : sda_out <=SLAVE_ADDR[5];
                7'd9 : scl <=1'b1;
             //7'd10 : scl <=1'b1;
               7'd11 : scl <=1'b0;
               7'd12 : sda_out <=SLAVE_ADDR[4];
               7'd13 : scl <=1'b1;
             //7'd14 : scl <=1'b1;
               7'd15 : scl <=1'b0;
               7'd16 : sda_out <=SLAVE_ADDR[3];
               7'd17 : scl <=1'b1;
             //7'd18 : scl <=1'b1;
               7'd19 : scl <=1'b0;
               7'd20 : sda_out <=SLAVE_ADDR[2];
               7'd21 : scl <=1'b1;
             //7'd22 : scl <=1'b1;
               7'd23 : scl <=1'b0;
               7'd24 : sda_out <=SLAVE_ADDR[1];
               7'd25 : scl <=1'b1;
             //7'd26 : scl <=1'b1;
               7'd27 : scl <=1'b0;
               7'd28 : sda_out <=SLAVE_ADDR[0];
               7'd29 : scl <=1'b1;
             //7'd30 : scl <=1'b1;
               7'd31 : scl <=1'b0;
               7'd32 : sda_out <=1'b1;//主机控制，输出模式，读从机发的地址
               7'd33 : scl <=1'b1;
             //7'd34 : scl <=1'b1;
               7'd35 : scl <=1'b0;
               7'd36 : 
                 begin
                   sda_dir <= 1'b0;            
                   sda_out <= 1'b1;
                 end
                7'd37 : scl <=1'b1;
                7'd38 :
                  begin
                    st_done <= 1'b1;
                    if(sda_in == 1'b1)  //sda,释放总线后等待从机应答信号
                      iic_ack <= 1'b1;
                  end
                7'd39 : 
                  begin
                    scl <= 1'b0;
                    cnt <= 1'b0; 
                  end 
                default : ;
              endcase
            end
          st_data_rd :
            begin
              case(cnt)
              //data_r,用于逐位从sda线上读取数据
                7'd0 : sda_dir <= 1'b0; //从机控制在scl,上升沿写入数据到sda
                7'd1 : 
                  begin
                    data_r[7] <=sda_in;
                    scl <=1'b1;
                  end
                7'd2 : scl <=1'b1;
                7'd3 : scl <=1'b0;
                7'd4 : sda_out <=SLAVE_ADDR[6];
                7'd5 : 
                begin
                  data_r[6] <=sda_in;
                  scl <=1'b1;
                end
                7'd6 : scl <=1'b1;
                7'd7 : scl <=1'b0;
                7'd8 : sda_out <=SLAVE_ADDR[5];
                7'd9 : 
                  begin
                    data_r[5] <=sda_in;
                    scl <=1'b1;
                  end
               7'd10 : scl <=1'b1;
               7'd11 : scl <=1'b0;
               7'd12 : sda_out <=SLAVE_ADDR[4];
               7'd13 : 
                  begin
                    data_r[4] <=sda_in;
                    scl <=1'b1;
                  end
               7'd14 : scl <=1'b1;
               7'd15 : scl <=1'b0;
               7'd16 : sda_out <=SLAVE_ADDR[3];
               7'd17 : 
                  begin
                    data_r[3] <=sda_in;
                    scl <=1'b1;
                  end
               7'd18 : scl <=1'b1;
               7'd19 : scl <=1'b0;
               7'd20 : sda_out <=SLAVE_ADDR[2];
               7'd21 : 
                  begin
                    data_r[2] <=sda_in;
                    scl <=1'b1;
                  end
               7'd22 : scl <=1'b1;
               7'd23 : scl <=1'b0;
               7'd24 : sda_out <=SLAVE_ADDR[1];
               7'd25 : 
                  begin
                    data_r[1] <=sda_in;
                    scl <=1'b1;
                  end
               7'd26 : scl <=1'b1;
               7'd27 : scl <=1'b0;
               7'd28 : sda_out <=SLAVE_ADDR[0];
               7'd29 : 
                  begin
                    data_r[0] <=sda_in;
                    scl <=1'b1;
                  end
               7'd30 : scl <=1'b1;
               7'd31 : scl <=1'b0;
               7'd32 : 
                 begin
                   sda_dir <= 1'b1;  //主机输出模式         
                   sda_out <= 1'b1;
                 end
               7'd33 : scl <=1'b1;//延后一个时刻确保从机在上升沿采样完成，避免主从机产生冲突
               7'd34 : st_done <=1'b1;
               7'd35 : 
                 begin
                   scl <=1'b0;
                   cnt <= 7'b0;
                   i2c_data_r <= data_r; //将读到的数据发送给主机
                 end
                default : ;
              endcase
            end
          st_stop : 
            begin
              case(cnt)
                7'd0 : 
                  begin
                    scl <=1'b1;
                    sda_out <= 1'b0;
                  end
                7'd1 : scl <=1'b1;
                7'd2 : scl <=1'b1;
                7'd3 : sda_out <=1'b1; //stop标志
                
               7'd15 : st_done <= 1'b1;
               7'd16 :
               begin
                 cnt      <= 7'b0;
                 i2c_done <= 1'b1; 
               end
               default : ;
             endcase
            end
          
        endcase 
    end             
  end
endmodule  